package vg.modules.navigator;


import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.List;

import javax.swing.*;
import javax.swing.tree.TreePath;

import vg.services.data_base_manager.data.graph.Attribute;
import vg.services.data_base_manager.data.graph.Vertex;
import vg.services.data_base_manager.data.header.AttributeHeader;
import vg.services.data_base_manager.data.header.VertexHeader;
import vg.services.data_base_manager.data.skeleton.GraphTreeElement;
import vg.services.main_manager.MainManager;
import vg.services.plugin_manager.event_and_request.event.AUIEvent;
import vg.services.plugin_manager.event_and_request.event.UIEventCloseTab;
import vg.services.plugin_manager.event_and_request.event.UIEventOpenNewGraph;
import vg.services.plugin_manager.event_and_request.event.UIEventOpenNewTab;
import vg.services.plugin_manager.event_and_request.event.UIEventRenameTab;
import vg.services.plugin_manager.event_and_request.event.graph_editor.AddAttributeEvent;
import vg.services.plugin_manager.event_and_request.event.graph_editor.AddVertexEvent;
import vg.services.plugin_manager.realization.PluginParameter;
import vg.services.user_interface_manager.additional_swing_components.ValueComboBox;
import vg.services.user_interface_manager.interfaces.AUserInterfaceElement;
import vg.services.user_interface_manager.interfaces.UserInterfaceObserverAdapter;

/**
 * This class realizes a graph navigator. 
 *
 * @author Timur Zolotuhin (e-mail: tzolotuhin@gmail.com)
 */
public class NavigatorPanel extends AUserInterfaceElement { 
	private final PluginParameter parameter;

	// Swing components
	private final JPanel view;
	private final JTree graph_tree, tab_tree;
	private final JScrollPane scrollPane;
	private final JButton prevSearchButton, nextSearchButton;
	private final ValueComboBox searchValueComboBox;

	// Main data
	private GraphTreeElement graph_root_node;
	private GraphTreeElement tab_root_node;
	private NodeManager graph_nodeManager, tab_nodeManager; // manages popup menu and key action of tree
	private List<GraphTreeElement> listNodes; // it need for high-speed performance. We don't need to round the tree. It's very slow.

	// Mutex
	private final Object generalMutex = new Object();

    /**
	 * Constructor.
	 * This method by need called from EDT only!!!
	 */
	public NavigatorPanel(PluginParameter pluginParameter) {
		super("Navigator", null);
		this.parameter = pluginParameter;
		//create panel for trees (graph tree and tab tree)
		NavigatorTreeRenderer renderer = new NavigatorTreeRenderer();
		this.graph_root_node = new GraphTreeElement("Graphs");
		this.graph_tree = new JTree(graph_root_node.getRoot());
		this.graph_tree.setCellRenderer(renderer);
		
		this.tab_root_node = new GraphTreeElement("Tabs");
		this.tab_tree = new JTree(tab_root_node.getRoot());
		this.tab_tree.setCellRenderer(renderer);
		
		JPanel tmp_panel = new JPanel(new GridBagLayout());
		GridBagConstraints gbc = new GridBagConstraints(0,0,  1,1,  1,0,  GridBagConstraints.NORTH, GridBagConstraints.HORIZONTAL, new Insets(1, 1, 1, 1),  0,0);
		tmp_panel.add(graph_tree, gbc);
		
		gbc = new GridBagConstraints(0,1,  1,1,  1,1,  GridBagConstraints.NORTH, GridBagConstraints.HORIZONTAL, new Insets(1, 1, 1, 1),  0,0);
		tmp_panel.add(tab_tree, gbc);
		
		this.scrollPane = new JScrollPane(tmp_panel);
		this.graph_nodeManager = new NodeManager(graph_tree, pluginParameter);
		this.tab_nodeManager = new NodeManager(tab_tree, pluginParameter);
		//create view
		this.view = new JPanel();
		this.view.setLayout(new GridBagLayout());
		
		gbc = new GridBagConstraints(0,0,  3,1,  1,1,  GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5),  0,0);		
		this.view.add(this.scrollPane, gbc);
		
		this.searchValueComboBox = new ValueComboBox();
		this.searchValueComboBox.setShowPopupDuringKeyPressed(false);
		this.searchValueComboBox.addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent e) {
				searchItem();
			}
		});
		this.searchValueComboBox.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				searchItem();
			}
		});
		
		gbc = new GridBagConstraints(0,1,  1,1,  1,0,  GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5),  0,0);
		this.view.add(this.searchValueComboBox.getView(), gbc);
		
		
		this.prevSearchButton = new JButton(new ImageIcon("./data/resources/textures/navigator/prev.png"));
		this.prevSearchButton.setToolTipText("Previous search result");
		this.prevSearchButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				synchronized (generalMutex) {
					selectPrevItem();
				}
			}
		});
		gbc = new GridBagConstraints(1,1,  1,1,  0,0,  GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5),  0,0);
		this.view.add(this.prevSearchButton, gbc);
		
		this.nextSearchButton = new JButton(new ImageIcon("./data/resources/textures/navigator/next.png"));
		this.nextSearchButton.setToolTipText("Next search result");
		this.nextSearchButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				synchronized (generalMutex) {
					selectNextItem();
				}
			}
		});
		gbc = new GridBagConstraints(2,1,  1,1,  0,0,  GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5),  0,0);
		this.view.add(this.nextSearchButton, gbc);

        pluginParameter.userInterfaceManager.addObserver(new UserInterfaceObserverAdapter() {
            @Override
            public void dispatchEvent(AUIEvent event) {
                switch(event.getType()) {
                    case DEF_OPEN_NEW_GRAPH: {
                        UIEventOpenNewGraph bufEvent = (UIEventOpenNewGraph)event;
                        add_graph(bufEvent.getGraphId());
                        break;
                    }

                    case DEF_OPEN_NEW_TAB: {
                        UIEventOpenNewTab bufEvent = (UIEventOpenNewTab)event;
                        add_tab_in_navigator(bufEvent.getTabId(), bufEvent.getTab().getTabTitle());
                        break;
                    }

                    case DEF_CLOSE_TAB: {
                        UIEventCloseTab bufEvent = (UIEventCloseTab)event;
                        close_tab(bufEvent.getTabId());
                        break;
                    }

                    case DEF_RENAME_TAB: {
                        final UIEventRenameTab bufEvent = (UIEventRenameTab)event;
                        synchronized (generalMutex) {
                            GraphTreeElement tabNode = tab_root_node.vg_getNode(bufEvent.getTabId(), GraphTreeElement.DEF_TAB_TYPE);
                            if(tabNode != null) {
                                tabNode.vg_setName(bufEvent.getNewName());
                                tab_tree.updateUI();
                            } else {
                                MainManager.logger.printError("[" + this.getClass().getName()+".update] [BAD] Renaming of tab.");
                            }
                        }
                        break;
                    }

                    case DEF_CHANGE_UI_STYLE: {
                        synchronized (generalMutex) {
                            graph_nodeManager.updateTheme();
                            tab_nodeManager.updateTheme();
                            SwingUtilities.updateComponentTreeUI(view);
                        }
                        break;
                    }

                    case DEF_RESET: {
                        synchronized (generalMutex) {
                            graph_root_node.removeAllChildren();
                            graph_tree.updateUI();
                        }
                        break;
                    }

                    case DEF_ADD_VERTEX: {
                        AddVertexEvent bufEvent = (AddVertexEvent)event;
                        add_vertex(bufEvent.getVertex());
                        break;
                    }

                    case DEF_ADD_ATTRIBUTE: {
                        AddAttributeEvent bufEvent = (AddAttributeEvent)event;
                        add_attribute(bufEvent.getAttr());
                        break;
                    }

                    default:
                        break;
                }
            }
        });
	}

    @Override
	public JComponent getView() {
		return view;
	}
//=============================================================================
//-----------PRIVATE METHODS---------------------------------------------------
	/**
	 * This method adds new tab in navigator, without updating of the tree.
	 * Don't calls methods  revalidate and updateUI at scrollBar, because we think that after this method 
	 * will called method selectTabInNavigator(there're this method is called). It is better for performance.  
	 */
	private void add_tab_in_navigator(int tabId, String tabName) {
		GraphTreeElement tab_node = new GraphTreeElement(tabName);
		tab_node.vg_setTab_id(tabId);
		tab_root_node.add(tab_node);
		if(SwingUtilities.isEventDispatchThread()) {
			tab_tree.updateUI();
		} else {
			SwingUtilities.invokeLater(new Runnable() {
				@Override
				public void run() {
					tab_tree.updateUI();
				}
			});
		}
	}
	
	/**
	 * This method replaces name in vertex in navigator.
	 */
	private void add_attribute(Attribute attr) {
		List<String>nodeList = null;
		synchronized (generalMutex) {
			String name = null;
			if (attr.isStringType() && attr.getName().equals("name"))
				name = attr.getStringValue();
			AttributeHeader ah = MainManager.graphDataBase.getAttributeHeader(attr.getStorableId());

            if (ah.getOwnerType() == AttributeHeader.EDGE_OWNER_TYPE)
                return;

            graph_root_node.vg_replaceName(name, ah.getOwnerType(), GraphTreeElement.DEF_VERTEX_TYPE);
			GraphTreeElement node = graph_root_node.vg_getNode(ah.getOwnerType(), GraphTreeElement.DEF_VERTEX_TYPE);
			nodeList = node.toStringList();
			listNodes = graph_root_node.toList();
		}
		if(SwingUtilities.isEventDispatchThread()) {
			synchronized (generalMutex) {
				searchValueComboBox.addItems(nodeList);
				searchValueComboBox.searchReset();
				graph_tree.updateUI();
			}
		} else {
			final List<String> finalNodeList = nodeList;
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					synchronized (generalMutex) {
						searchValueComboBox.addItems(finalNodeList);
						searchValueComboBox.searchReset();
						graph_tree.updateUI();
					}
				}
			});
		}
	}
	
	/**
	 * This method adds new vertex in navigator.
	 */
	private void add_vertex(Vertex vertex) {
		List<String>nodeList = null;
		synchronized (generalMutex) {
			String name = null;
			Attribute attr = vertex.getAttribute("name");
			if (attr != null && attr.isStringType())
				name = attr.getStringValue();
			
			GraphTreeElement node = new GraphTreeElement(name);
			node.vg_setVertex_id(vertex.getStorableId());
			VertexHeader vh = MainManager.graphDataBase.getVertexHeader(vertex.getStorableId());
			graph_root_node.vg_insert(node, vh.subgraph_id, GraphTreeElement.DEF_GRAPH_TYPE);
			nodeList = node.toStringList();
			listNodes = graph_root_node.toList();
		}
		if(SwingUtilities.isEventDispatchThread()) {
			synchronized (generalMutex) {
				searchValueComboBox.addItems(nodeList);
				searchValueComboBox.searchReset();
				graph_tree.updateUI();
			}
		} else {
			final List<String> finalNodeList = nodeList;
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					synchronized (generalMutex) {
						searchValueComboBox.addItems(finalNodeList);
						searchValueComboBox.searchReset();
						graph_tree.updateUI();
					}
				}
			});
		}
	}
	
	/**
	 * This method adds new graph in navigator.
	 * @param graphId - id of graph.
	 */
	private void add_graph(int graphId) {
		List<String>nodeList = null;
		synchronized (generalMutex) {
			GraphTreeElement node = parameter.graphDataBase.getGraphModelTree(graphId);
			if (node != null) {
				graph_root_node.vg_add(node);
				nodeList = node.toStringList();
				listNodes = graph_root_node.toList();
			}
		}
		if(SwingUtilities.isEventDispatchThread()) {
			synchronized (generalMutex) {
				searchValueComboBox.addItems(nodeList);
				searchValueComboBox.searchReset();
				graph_tree.updateUI();
			}
		} else {
			final List<String> finalNodeList = nodeList;
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					synchronized (generalMutex) {
						searchValueComboBox.addItems(finalNodeList);
						searchValueComboBox.searchReset();
						graph_tree.updateUI();
					}
				}
			});
		}
	}
	
	/**
	 * This method closes the tab.
	 * @param tabId - id of the tab.
	 */
	private void close_tab(int tabId) {
		synchronized (generalMutex) {
			tab_root_node.vg_remove(tabId, GraphTreeElement.DEF_TAB_TYPE);
			if(SwingUtilities.isEventDispatchThread()) {
				tab_tree.updateUI();
			} else {
				SwingUtilities.invokeLater(new Runnable() {
					public void run() {
						synchronized (generalMutex) {
							tab_tree.updateUI();
						}
					}
				});
			}
		}
	}
	private void searchItem() {
		if(this.listNodes == null) return;
		String substring = this.searchValueComboBox.getSelectedItem();
		if(substring != null) {
			for(GraphTreeElement buf : this.listNodes) {
				if(buf != null) {
					String s = buf.toString();
					if(s != null && s.indexOf(substring) >= 0) {
						select_node_in_navigator(buf);
						break;
					}
				}
			}
		}
	}
	private void selectNextItem() {
		if(this.listNodes == null) return;
		int currentSearchResult = -1;
		if(graph_tree.getSelectionCount() == 1) {
			TreePath tp = graph_tree.getSelectionPath();
			if(tp != null) {
				GraphTreeElement gn = graph_root_node.vg_getNode(tp);
				currentSearchResult = this.listNodes.indexOf(gn);
			}
		}
		boolean check = false;
		String substring = this.searchValueComboBox.getSelectedItem();
		if(substring != null) {
			for(int i = currentSearchResult + 1; i < this.listNodes.size(); i++) {
				GraphTreeElement buf = this.listNodes.get(i);
				if(buf != null) {
					String s = buf.toString();
					if(s != null && s.indexOf(substring) >= 0) {
						select_node_in_navigator(buf);
						check = true;
						break;
					}
				}
			}
		}
		if(!check) {
			//searchItem();
		}
	}
	private void selectPrevItem() {
		if(this.listNodes == null) return;
		int currentSearchResult = -1;
		if(graph_tree.getSelectionCount() == 1) {
			TreePath tp = graph_tree.getSelectionPath();
			if(tp != null) {
				GraphTreeElement gn = graph_root_node.vg_getNode(tp);
				currentSearchResult = this.listNodes.indexOf(gn);
			}
		}
		boolean check = false;
		String substring = this.searchValueComboBox.getSelectedItem();
		if(substring != null) {
			for(int i = currentSearchResult - 1; i > 0; i--) {
				GraphTreeElement buf = this.listNodes.get(i);
				if(buf != null) {
					String s = buf.toString();
					if(s != null && s.indexOf(substring) >= 0) {
						select_node_in_navigator(buf);
						check = true;
						break;
					}
				}
			}
		}
		if(!check) {
			//searchItem();
		}
	}
	
	/**
	 * This method selects node in navigator.
	 */
	private void select_node_in_navigator(final GraphTreeElement node) {	
		if(!SwingUtilities.isEventDispatchThread()) {
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					select_node_in_navigator(node);
				}
			});
		} else {
			synchronized (generalMutex) {
				TreePath path = graph_root_node.vg_getPath(node);
				if(path != null) {
					graph_tree.setSelectionPath(path);
					int row = graph_tree.getRowForPath(path);
					graph_tree.scrollRowToVisible(row);
					graph_tree.updateUI();
					MainManager.logger.printDebug("[" + this.getClass().getName()+".select_node_in_navigator] [OK] Select node (in navigator).");
				} else {
					MainManager.logger.printDebug("[" + this.getClass().getName()+".select_node_in_navigator] [BAD] Select node (in navigator).");
				}
			}
		}
	}
}